package com.mini;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;

public class MiniBeanProcessor {
    /* service provider interface（简称SPI技术）
     * 利用该技术可以通过接口动态的获取接口所有的实现类，提高了代码后期的可维护性和拓展性
     * 需要添加该文件 META-INF/services/com.mini.MiniColumnHandler 声明实现类的全限定名 */
    private static final ServiceLoader<MiniColumnHandler> columnHandlers = ServiceLoader.load(MiniColumnHandler.class);
    // 用特殊的数值来表示元数据的结果列没有匹配的JavaBean属性
    protected static final int PROPERTY_NOT_FOUND = -1;
    // 当返回的值为空时，给实例对象的属性设置的原始值
    private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>();
    // 用于指定实体类属性映射到数据库字段的名称，通常在实体类属性名与数据库字段名不完全一致时使用
    private final Map<String, String> columnToPropertyOverrides;

    // 实例对象属性的原始值初始化
    static {
        primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
        primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0));
        primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0));
        primitiveDefaults.put(Float.TYPE, Float.valueOf(0f));
        primitiveDefaults.put(Double.TYPE, Double.valueOf(0d));
        primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
    }

    public MiniBeanProcessor() {
        this(new HashMap<String, String>());
    }
    public MiniBeanProcessor(Map<String, String> columnToPropertyOverrides) {
        super();
        // 由使用者指定实体类属性应该映射到的数据库的列名，但是不能传递一个空的映射
        if (columnToPropertyOverrides == null) {
            throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null 映射容器不能为空");
        }
        this.columnToPropertyOverrides = columnToPropertyOverrides;
    }

    // 将当前行的查询结果封装到实例对象中
    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
        // 获取到这个JavaBean所有属性的的导出信息
        PropertyDescriptor[] props = this.propertyDescriptors(type);
        // 获取当前行的元数据
        ResultSetMetaData rsmd = rs.getMetaData();
        // 检索结果集列名与导出的实例属性索引位置的对应关系
        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
        // 返回创建的实例对象
        return this.createBean(rs, type, props, columnToProperty);
    }

    // 创建一个实体类对象并根据结果集和导出属性的位置关系初始化这个对象的属性值
    private <T> T createBean(ResultSet rs, Class<T> type, PropertyDescriptor[] props, int[] columnToProperty)
            throws SQLException {
        // 获取字节码对象的实例对象
        T bean = this.newInstance(type);
        // 根据记录到的位置关系的数量
        for (int i = 1; i < columnToProperty.length; i++) {
            // 如果记录的是没有配置到JavaBean属性就跳过以下步骤进入下一次循环
            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                continue;
            }
            // 根据索引位置关系取出当前列对应的Bean实例的导出属性
            PropertyDescriptor prop = props[columnToProperty[i]];
            // 获取当前导出属性的属性类型
            Class<?> propType = prop.getPropertyType();

            Object value = null;
            if(propType != null) {
                // 从结果集中获取需要填充到实例属性的属性值
                value = this.processColumn(rs, i, propType);
                // 如果取出的属性值为空并且属性类型属于基本类型，就将属性值设置为默认值
                if (value == null && propType.isPrimitive()) {
                    value = primitiveDefaults.get(propType);
                }
            }
            // 根据导出属性，将导出属性的属性值赋值到实例对象的属性中
            this.callSetter(bean, prop, value);
        }

        return bean;
    }
    // 根据反射创建字节码对象的实例对象
    protected <T> T newInstance(Class<T> c) throws SQLException {
        try {
            return c.newInstance();
            // 捕获反射的异常抛出一个新的异常，以便让上层在调用的时候知道发生了什么
        } catch (InstantiationException e) {
            throw new SQLException(
                    "Cannot create " + c.getName() + ": " + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new SQLException(
                    "Cannot create " + c.getName() + ": " + e.getMessage());
        }
    }
    // 取出结果集的列值并转为对应的java类型数据
    protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
        // 如果实体属性可以兼容空值并且获取到的结果为空，将直接返回空
        if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
            return null;
        }

        /* 在老版本《1.6》中使用，《1.7》版本已被弃用 */
//        if (propType.equals(String.class)) {
//            return rs.getString(index);
//
//        } else if (
//                propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
//            return Integer.valueOf(rs.getInt(index));
//
//        } else if (
//                propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
//            return Boolean.valueOf(rs.getBoolean(index));
//
//        } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
//            return Long.valueOf(rs.getLong(index));
//
//        } else if (
//                propType.equals(Double.TYPE) || propType.equals(Double.class)) {
//            return Double.valueOf(rs.getDouble(index));
//
//        } else if (
//                propType.equals(Float.TYPE) || propType.equals(Float.class)) {
//            return Float.valueOf(rs.getFloat(index));
//
//        } else if (
//                propType.equals(Short.TYPE) || propType.equals(Short.class)) {
//            return Short.valueOf(rs.getShort(index));
//
//        } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
//            return Byte.valueOf(rs.getByte(index));
//
//        } else if (propType.equals(Timestamp.class)) {
//            return rs.getTimestamp(index);
//
//        } else if (propType.equals(SQLXML.class)) {
//            return rs.getSQLXML(index);
//
//        } else {
//            return rs.getObject(index);
//        }
        /* 新版《1.7》的执行方式 - SPI技术（通过接口获取实现类）
         * 先通过getObject获取备用数据，以防没有匹配到属性的类型也可以使用 */
        Object retval = rs.getObject(index);
        // 使用SPI技术可以增强代码的维护性和可拓展性
        for (MiniColumnHandler handler : columnHandlers) {
            // 如果列类型处理器处理的类型与实体类当前属性的类型一致才处理
            if (handler.match(propType)) {
                retval = handler.apply(rs, index);
                break;
            }
        }
        return retval;
    }
    // 调用实例对象导出属性的set方法赋值
    private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException {
        // 获取导出属性的写入方法
        Method setter = prop.getWriteMethod();

        if (setter == null) {
            return;
        }
        // 获取set方法所有参数的类型
        Class<?>[] params = setter.getParameterTypes();
        try {
            // 判断属性值是否是Date或者Date的子类对象
            if (value instanceof java.util.Date) {
                // 获取方法第一个参数的类型名称
                final String targetType = params[0].getName();
                // 根据参数类型对属性值进行强制转换
                if ("java.sql.Date".equals(targetType)) {
                    value = new java.sql.Date(((java.util.Date) value).getTime());
                } else
                if ("java.sql.Time".equals(targetType)) {
                    value = new java.sql.Time(((java.util.Date) value).getTime());
                } else
                if ("java.sql.Timestamp".equals(targetType)) {
                    Timestamp tsValue = (Timestamp) value;
                    int nanos = tsValue.getNanos();
                    value = new java.sql.Timestamp(tsValue.getTime());
                    ((Timestamp) value).setNanos(nanos);
                }
            } else
                // 如果属性值是String或String的子类对象并且方法的第一个参数被声明为枚举类型
            if (value instanceof String && params[0].isEnum()) {
                value = Enum.valueOf(params[0].asSubclass(Enum.class), (String) value);
            }

            if (this.isCompatibleType(value, params[0])) {
                // 将属性值赋值到实例对象中
                setter.invoke(target, new Object[]{value});
            } else {
                throw new SQLException(
                        "Cannot set " + prop.getName() + ": incompatible types, cannot convert "
                                + value.getClass().getName() + " to " + params[0].getName());
                // value cannot be null here because isCompatibleType allows null
            }

        } catch (IllegalArgumentException e) {
            throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());

        } catch (IllegalAccessException e) {
            throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());

        } catch (InvocationTargetException e) {
            throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());
        }
    }
    // 判断属性值的类型是否被兼容
    private boolean isCompatibleType(Object value, Class<?> type) {
        if (value == null || type.isInstance(value)) {
            return true;

        } else if (type.equals(Integer.TYPE) && value instanceof Integer) {
            return true;

        } else if (type.equals(Long.TYPE) && value instanceof Long) {
            return true;

        } else if (type.equals(Double.TYPE) && value instanceof Double) {
            return true;

        } else if (type.equals(Float.TYPE) && value instanceof Float) {
            return true;

        } else if (type.equals(Short.TYPE) && value instanceof Short) {
            return true;

        } else if (type.equals(Byte.TYPE) && value instanceof Byte) {
            return true;

        } else if (type.equals(Character.TYPE) && value instanceof Character) {
            return true;

        } else if (type.equals(Boolean.TYPE) && value instanceof Boolean) {
            return true;

        }
        return false;

    }
    /* propertyDescriptor 导出一个属性的所有信息；可以通过该类给JavaBean进行赋值 */
    private PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException {
        BeanInfo beanInfo = null;
        try {
            /* 获取Bean实例所有的属性信息
             * Introspector 内省器。通过该类可以了解目标类支持的所有属性和方法等信息
             * beanInfo 包含JavaBean所有的属性、和公开的方法信息 */
            beanInfo = Introspector.getBeanInfo(c);

        } catch (IntrospectionException e) {
            throw new SQLException(
                    "Bean introspection failed: " + e.getMessage());
        }
        // 获取这个JavaBean所有属性的导出信息
        return beanInfo.getPropertyDescriptors();
    }
    /* 找到结果集合列位置和导出属性位置的映射关系 */
    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {
        // 获得当前行总的列数
        int cols = rsmd.getColumnCount();
        /* 创建容器，记录结果列与导出属性的位置映射关系
         * 数组长度在总列数的基础之上加1；是为了以后遍历数组时，列值的取值方便；因为结果集对象在取列值时是从索引位置1开始的 */
        int[] columnToProperty = new int[cols + 1];
        // 给数组的每个元素都赋值成PROPERTY_NOT_FOUND所表示的值，将初始的映射结果都初始化成没有匹配的JavaBean属性
        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
        // 遍历当前行的数据列
        for (int col = 1; col <= cols; col++) {
            // 获取当前列的别名，如果没有别名则获取列名
            String columnName = rsmd.getColumnLabel(col);
            if (null == columnName || 0 == columnName.length()) {
                columnName = rsmd.getColumnName(col);
            }
            /* 查看使用者是否指定了属性和字段的映射。如果未指定，则使用完全映射；如果指定了则使用指定的映射 */
            String propertyName = columnToPropertyOverrides.get(columnName);
            if (propertyName == null) {
                propertyName = columnName;
            }
            // 记录结果列与实例导出属性的一一对应关系
            for (int i = 0; i < props.length; i++) {
                // 不区分大小写的判断当前通过列名转换的属性名与导出的实例属性名是否完全一致；如果一致就将当前导出属性的索引位置记录到数组中
                if (propertyName.equalsIgnoreCase(props[i].getName())) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;
    }
}
